require 'socket'
require 'thread'
require 'documenttransferprotocol'

class DocumentTransferServerConn
  # Possiveis estados da conexao
  RECEIVING_REQ = 0
  SENDING_RESPONSE = 1
  RECEIVING_DOCUMENT = 2
  SENDING_RESPONSE_DATA = 3
  PROCESS = 4

  def initialize(unrealdb, server, socket)
    @server = server
    @socket = socket
    @state = RECEIVING_REQ
    @operation = -1
    @document_received_size = 0
    @document_size = 0
    @document_id = nil
    @document = ""
    @resp_status = REQ_OK
    @response_data_sent = 0
    @response_data_size = 0
    @response_data = ""
    @node_id = nil
    @db = unrealdb
  end

  def run
    while true
      process if @state == PROCESS

      read_array = []
      write_array = []
      error_array = []

      if @state == RECEIVING_REQ || @state == RECEIVING_DOCUMENT
        read_array << @socket
      elsif @state == SENDING_RESPONSE || @state == SENDING_RESPONSE_DATA
        write_array << @socket
      end

      result = select(read_array, write_array, error_array)

      next if result == nil

      for sock_read in result[0]
        if sock_read == @socket
          if @socket.eof?
            @server.conn_closed(self)
            return
          end

          if !do_read_state
            @server.conn_closed(self)
            return
          end
        end
      end

      for sock_write in result[1]
        if sock_write == @socket
          if !do_write_state
            @server.conn_closed(self)
            return
          end
        end
      end
    end   
  end

  def process
    case @operation
    when GET_DOCUMENT      
      doc = @db.get_local(@document_id, @node_id)
      if doc == nil
        #puts "[DocumentTransferServerConn] - Erro ao obter documento: " + @document_id + "@" + @node_id
        @resp_status = REQ_NOT_FOUND
        @response_data = nil
        @response_data_size = 0
      else
        #puts "[DocumentTransferServerConn] - Sucesso ao obter documento: " + @document_id + "@" + @node_id
        @resp_status = REQ_OK        
        @response_data = doc
        @response_data_size = doc.length
      end
      @state = SENDING_RESPONSE
    when GET_ALL_DOCUMENTS      
      zip = @db.get_all_files_local(@node_id)
      if zip == nil
        #puts "[DocumentTransferServerConn] - Erro ao obter todos os documentos: " + @src_host + "@" + @node_id
        @resp_status = REQ_NOT_FOUND
        @response_data = nil
        @response_data_size = 0
      else
        #puts "[DocumentTransferServerConn] - Sucesso ao obter todos os documentos: " + @src_host + "@" + @node_id
        @resp_status = REQ_OK
        @response_data = zip
        @response_data_size = zip.length
      end
      @state = SENDING_RESPONSE
    when UPDATE_DOCUMENT      
      if !@db.update_local(@document, @document_id, @node_id)
        #puts "[DocumentTransferServerConn] - Erro ao atualizar documento: " + @document_id + "@" + @node_id
        @resp_status = REQ_NOT_FOUND
      else
        #puts "[DocumentTransferServerConn] - Sucesso ao atualizar documento: " + @document_id + "@" + @node_id
        @resp_status = REQ_OK
      end
      @state = SENDING_RESPONSE
      @response_data = nil
      @response_data_size = 0
    when INSERT_DOCUMENT
      if !@db.put_local(@document, @document_id, @node_id)
        #puts "[DocumentTransferServerConn] - Erro ao inserir documento: " + @document_id + "@" + @node_id
        @resp_status = REQ_NOT_FOUND
      else
        #puts "[DocumentTransferServerConn] - Sucesso ao inserir documento: " + @document_id + "@" + @node_id
        @resp_status = REQ_OK
      end
      @state = SENDING_RESPONSE
      @response_data = nil
      @response_data_size = 0
    when REPLICATE_DOCUMENTS
      if !@db.receive_replicated_db(@node_id, @document, self)
        #puts "[DocumentTransferServerConn] - Erro ao receber replicas: " + @node_id + "@" + @src_host
        @resp_status = REQ_NOT_FOUND
      else
        #puts "[DocumentTransferServerConn] - Sucesso ao receber replicas: " + @node_id + "@" + @src_host
        @resp_status = REQ_OK
      end
      @state = SENDING_RESPONSE
      @response_data = nil
      @response_data_size = 0
    when INSERT_DOCUMENTS
      if !@db.put_files_local(@node_id, @document)
        #puts "[DocumentTransferServerConn] - Erro ao inserir documentos: " + @node_id
        @resp_status = REQ_NOT_FOUND
      else
        #puts "[DocumentTransferServerConn] - Sucesso ao inserir documentos: " + @node_id
        @resp_status = REQ_OK
      end
      @state = SENDING_RESPONSE
      @response_data = nil
      @response_data_size = 0
    when DESTROY_REPLICA
      if !@db.destroy_replica(@node_id)
        #puts "[DocumentTransferServerConn] - Erro ao destruir replica: " + @node_id
        @resp_status = REQ_NOT_FOUND
      else
        #puts "[DocumentTransferServerConn] - Sucesso ao destruir replica: " + @node_id
        @resp_status = REQ_OK
      end
      @state = SENDING_RESPONSE
      @response_data = nil
      @response_data_size = 0
    when REMOVE_DOCUMENT      
      if !@db.remove_local(@document_id, @node_id)
        #puts "[DocumentTransferServerConn] - Erro ao remover documento: " + @document_id + "@" + @node_id
        @resp_status = REQ_NOT_FOUND
      else
        #puts "[DocumentTransferServerConn] - Sucesso ao remover documento: " + @document_id + "@" + @node_id
        @resp_status = REQ_OK
      end
      @state = SENDING_RESPONSE
      @response_data = nil
      @response_data_size = 0
    else
      return false
    end
    return true
  end

  def do_read_state
    case @state
    when RECEIVING_REQ
      receive_req
    when RECEIVING_DOCUMENT
      receive_document
    else
      return false
    end
    return true
  end

  def do_write_state
    case @state
    when SENDING_RESPONSE
      send_response
    when SENDING_RESPONSE_DATA
      send_response_data
    else
      return false
    end
    return true
  end

  def receive_document
    expected_size = @document_size - @document_received_size

    data = @socket.read(expected_size)

    return false if data == nil

    if data.length != expected_size
      @document += data
      @document_received_size -= data.length
      return true
    end

    @document_received_size = 0
    @document += data
    @state = PROCESS

    return true
  end

  def receive_req
    begin
      data = @socket.readline
    rescue
      return false
    end

    return false if data == nil

    array = data.split(";")

    @operation = Integer(array[0])
    @document_id = array[1]
    @node_id = array[2]
    @document_size = Integer(array[3])
    @src_host = array[4].strip

    case @operation
    when GET_DOCUMENT
      @state = PROCESS
    when GET_ALL_DOCUMENTS
      @state = PROCESS
    when UPDATE_DOCUMENT
      @state = RECEIVING_DOCUMENT
    when INSERT_DOCUMENT
      @state = RECEIVING_DOCUMENT
    when REMOVE_DOCUMENT
      @state = PROCESS
    when REPLICATE_DOCUMENTS
      @state = RECEIVING_DOCUMENT
    when INSERT_DOCUMENTS
      @state = RECEIVING_DOCUMENT
    when DESTROY_REPLICA
      @state = PROCESS
    else
      return false
    end

    return true
  end

  def send_response
    packed = @resp_status.to_s + ";" + @response_data_size.to_s

    @socket.puts(packed)

    if @response_data_size != 0
      @state = SENDING_RESPONSE_DATA
    else
      flush_vars
    end

    return true
  end

  def send_response_data    
    remaining_size = @response_data_size - @response_data_sent

    data = @socket.send(@response_data[@response_data_sent..-1], 0)

    return false if data == nil

    if data != remaining_size
      @response_data_sent += data
      return true
    end

    flush_vars
    return true
  end

  def close
    @socket.close
  end

private

  def flush_vars
    @state = RECEIVING_REQ
    @operation = -1
    @document_received_size = 0
    @document_size = 0
    @document_id = nil
    @document = ""
    @resp_status = REQ_OK
    @response_data_sent = 0
    @response_data_size = 0
    @response_data = ""
    @node_id = nil
  end
end
